/**
 * Created by lycheng on 2019/8/1.
 * 
 * 语音听写流式 WebAPI 接口调用示例 接口文档（必看）：https://doc.xfyun.cn/rest_api/语音听写（流式版）.html
 * webapi 听写服务参考帖子（必看）：http://bbs.xfyun.cn/forum.php?mod=viewthread&tid=38947&extra=
 * 语音听写流式WebAPI 服务，热词使用方式：登陆开放平台https://www.xfyun.cn/后，找到控制台--我的应用---语音听写---个性化热词，上传热词
 * 注意：热词只能在识别的时候会增加热词的识别权重，需要注意的是增加相应词条的识别率，但并不是绝对的，具体效果以您测试为准。
 * 错误码链接：
 * https://www.xfyun.cn/doc/asr/voicedictation/API.html#%E9%94%99%E8%AF%AF%E7%A0%81
 * https://www.xfyun.cn/document/error-code （code返回错误码时必看）
 * 语音听写流式WebAPI 服务，方言或小语种试用方法：登陆开放平台https://www.xfyun.cn/后，在控制台--语音听写（流式）--方言/语种处添加
 * 添加后会显示该方言/语种的参数值
 * 
 */
import CryptoJS from 'crypto-js'
 
// 音频转码worker
let recorderWorker = new Worker('static/plugins/audio/transformpcm.worker.js')
// 记录处理的缓存音频
let buffer = []
let AudioContext = window.AudioContext || window.webkitAudioContext
let notSupportTip = '语义识别配置错误，请联系管理员'
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia

recorderWorker.onmessage = function (e) {
  buffer.push(...e.data.buffer)
}

class IatRecorder {
  constructor (config) {
    this.config = config
    this.state = 'ing'
    this.language = config.language || 'zh_cn'
    this.accent = config.accent || 'mandarin'

    //以下信息在控制台-我的应用-语音听写（流式版）页面获取
    this.appId = '5d09ba7b' 
    this.apiKey = '662f582af79c1f6d53ce986487f2d21d'
    this.apiSecret = '937454d41dd12fffca607e1ef6016fa2'
  }

  start () {
    this.stop()
    if (navigator.getUserMedia && AudioContext) {
      this.state = 'ing'
      if (!this.recorder) {
        var context = new AudioContext()
        this.context = context
        this.recorder = context.createScriptProcessor(0, 1, 1)

        var getMediaSuccess = (stream) => {
          var mediaStream = this.context.createMediaStreamSource(stream)
          this.mediaStream = mediaStream
          this.recorder.onaudioprocess = (e) => {
            this.sendData(e.inputBuffer.getChannelData(0))
          }
          this.connectWebsocket()
        }
        var getMediaFail = (e) => {
          this.recorder = null
          this.mediaStream = null
          this.context = null
          //console.log('请求麦克风失败')
        }
        if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
          navigator.mediaDevices.getUserMedia({
            audio: true,
            video: false
          }).then((stream) => {
            getMediaSuccess(stream)
          }).catch((e) => {
            getMediaFail(e)
          })
        } else {
          navigator.getUserMedia({
            audio: true,
            video: false
          }, (stream) => {
            getMediaSuccess(stream)
          }, function (e) {
            getMediaFail(e)
          })
        }
      } else {
        this.connectWebsocket()
      }
    } else {
      var isChrome = navigator.userAgent.toLowerCase().match(/chrome/)
    }
  }

  stop () {
    this.state = 'end'
    try {
      this.mediaStream.disconnect(this.recorder)
      this.recorder.disconnect()
    } catch (e) {}
  }

  sendData (buffer) {
    recorderWorker.postMessage({
      command: 'transform',
      buffer: buffer
    })
  }
  connectWebsocket () {
    var url = 'wss://iat-api.xfyun.cn/v2/iat'
    var host = 'iat-api.xfyun.cn'
    var apiKey = this.apiKey
    var apiSecret = this.apiSecret
    var date = new Date().toGMTString()
    var algorithm = 'hmac-sha256'
    var headers = 'host date request-line'
    var signatureOrigin = `host: ${host}\ndate: ${date}\nGET /v2/iat HTTP/1.1`
    var signatureSha = CryptoJS.HmacSHA256(signatureOrigin, apiSecret)
    var signature = CryptoJS.enc.Base64.stringify(signatureSha)
    var authorizationOrigin = `api_key="${apiKey}", algorithm="${algorithm}", headers="${headers}", signature="${signature}"`
    var authorization = btoa(authorizationOrigin)
    url = `${url}?authorization=${authorization}&date=${date}&host=${host}`

    if ('WebSocket' in window) {
      this.ws = new WebSocket(url)
    } else if ('MozWebSocket' in window) {
      this.ws = new MozWebSocket(url)
    } else {
      return null
    }
    this.ws.onopen = (e) => {
      this.mediaStream.connect(this.recorder)
      this.recorder.connect(this.context.destination)
      setTimeout(() => {
        this.wsOpened(e)
      }, 500)
      this.config.onStart && this.config.onStart(e)
    }
    this.ws.onmessage = (e) => {
      this.config.onMessage && this.config.onMessage(e)
      this.wsOnMessage(e)
    }
    this.ws.onerror = (e) => {
      this.stop()
      this.config.onError && this.config.onError(e)
    }
    this.ws.onclose = (e) => {
      this.stop()
      this.config.onClose && this.config.onClose(e)
    }
  }

  wsOpened () {
    if (this.ws.readyState !== 1) {
      return
    }
    var audioData = buffer.splice(0, 1280)
    //console.log('audioData', audioData.length)
    var params = {
      'common': {
        'app_id': this.appId
      },
      'business': {
        'language': this.language, //小语种可在控制台--语音听写（流式）--方言/语种处添加试用
        'domain': 'iat',
        'accent': this.accent, //中文方言可在控制台--语音听写（流式）--方言/语种处添加试用
        'vad_eos': 2500,
        'dwa': 'wpgs' //为使该功能生效，需到控制台开通动态修正功能（该功能免费）
      },
      'data': {
        'status': 0,
        'format': 'audio/L16;rate=16000',
        'encoding': 'raw',
        'audio': this.ArrayBufferToBase64(audioData)
      }
    }
    this.ws.send(JSON.stringify(params))
    this.handlerInterval = setInterval(() => {
      if(!this.ws) return;
      // websocket未连接
      if (this.ws.readyState !== 1) {
        clearInterval(this.handlerInterval)
        return
      }
      if (buffer.length === 0) {
        if (this.state === 'end') {
          this.ws.send(JSON.stringify({
            'data': {
              'status': 2,
              'format': 'audio/L16;rate=16000',
              'encoding': 'raw',
              'audio': ''
            }
          }))
          clearInterval(this.handlerInterval)
        }
        return false
      }
      audioData = buffer.splice(0, 1280)
      // 中间帧
      this.ws.send(JSON.stringify({
        'data': {
          'status': 1,
          'format': 'audio/L16;rate=16000',
          'encoding': 'raw',
          'audio': this.ArrayBufferToBase64(audioData)
        }
      }))
    }, 40)
  }

  wsOnMessage (e) {
    let jsonData = JSON.parse(e.data)
    // 识别结束
    if (jsonData.code === 0 && jsonData.data.status === 2 && this.ws) {
      this.ws.close()
    }
    if (jsonData.code !== 0 && this.ws) {
      this.ws.close()
      //console.log(`${jsonData.code}:${jsonData.message}`)
    }
  }

  ArrayBufferToBase64 (buffer) {
    var binary = ''
    var bytes = new Uint8Array(buffer)
    var len = bytes.byteLength
    for (var i = 0; i < len; i++) {
      binary += String.fromCharCode(bytes[i])
    }
    return window.btoa(binary)
  }

  close(){
    if(this.ws){
      clearInterval(this.handlerInterval)
      this.ws.close()
      this.ws=null;
    }
  }
}

class IatTaste {
  constructor () {
    var iatRecorder = new IatRecorder({
      onClose: () => {
        this.stop()
        //this.reset()
      },
      onError: (data) => {
        this.stop()
        //this.reset()
      },
      onMessage: (e) => {
        let str = ''
        let jsonData = JSON.parse(e.data) 
        //console.log("识别状态："+jsonData.code)
        if (jsonData.data) {
          if(jsonData.data.result){
            //console.log(jsonData.data.result)
            this.setResult(jsonData.data.result)
          }  
          //jsonData.data.status==2标识语义识别结束 
          if(jsonData.data.status && jsonData.data.status===2){ 
            //console.log("识别结束。。");    
            this.reset()
            this.stop()
            if(this.finalRText.indexOf('重说') < 0 && this.finalRText.indexOf('说错') < 0 && this.finalRText.indexOf('再来一遍') < 0) {
              // 设置最终识别结果
              window.FunList.excuteDiscernResult(this.finalRText)
            }
            this.resultText = ''
            this.finalRText=''
            // if(this.disStatus===1){  //识别结束，下一步执行的结果是结果命令词执行识别
            //      this.disStatus=2;
            // }else{
            //      this.disStatus=1; //识别结束，下一步执行的结果是命令词识别
            // }
          } 
        }
      },
      onStart: () => {
        //this.counterDown()
      }
    })
    this.iatRecorder = iatRecorder

    this.resultText = ''
    this.finalRText='' //最终结果
    // this.disStatus=1 //1命令词识别，2结果命令词执行词识别
  }

  start () {
    this.iatRecorder.start()
  }

  stop () {
    if(this.iatRecorder){
      this.iatRecorder.stop()
    }
  }

  reset () {
    //clearTimeout(this.counterDownTimeout)
    buffer = []
  }

  init () {
    let self = this
    // self.disStatus=1;
    if (navigator.getUserMedia && AudioContext && recorderWorker) {
      self.start()
    } else {
    }
  }
  setResult (data) {
    var str = ''
    var resultStr = ''
    let ws = data.ws
    for (let i = 0; i < ws.length; i++) {
      str = str + ws[i].cw[0].w
    }
    // 开启wpgs会有此字段(前提：在控制台开通动态修正功能)
    // 取值为 "apd"时表示该片结果是追加到前面的最终结果；取值为"rpl" 时表示替换前面的部分结果，替换范围为rg字段
    if (data.pgs === 'apd') {
      this.resultText = this.finalRText
    }
    resultStr = this.resultText + str
    this.finalRText=resultStr
    //console.log("识别结果内容："+this.finalRText)
    //逐渐推送识别结果
    window.FunList.setDiscernResult(resultStr);
  }
  close(){
    //clearTimeout(this.counterDownTimeout)
    this.stop()
    this.reset()
    this.resultText = ''
    this.finalRText=''
    this.iatRecorder.close()
    this.iatRecorder=null
  }
}
export default IatTaste